
import os
import numpy as np
import pandas as pd
from fastapi import UploadFile, File, HTTPException, APIRouter
from pydantic import BaseModel
from typing import List, Dict, Tuple
import uuid
import re
import chardet
from utils import run_in_thread, audit_action


powder_compounding_router = APIRouter()



# 核心功能配置
DEFAULT_OUTPUT_FOLDER = "./storage"
os.makedirs(DEFAULT_OUTPUT_FOLDER, exist_ok=True)

# --------------------------
# 数据模型
# --------------------------
class MergeRequest(BaseModel):
    filenames: List[str]
    user_id: str
    new_filename: str = None

class IntersectionRequest(BaseModel):
    filenames: List[str]
    user_id: str
    y_value: float = 0.5  # 仅水平线需要


class CalculationRequest(BaseModel):
    filenames: List[str]
    user_id: str
    selected_indices: List[int]
    m_value: float = 0.37
    y_value: float = 0.5  # 可选的水平线Y值


class CalculationRequest2(BaseModel):
    selected_points: List[Tuple[float, float, str]]
    m_value: float = 0.37
    weight_infos: Dict[str, float] = None

#获取该用户下所有文件

@powder_compounding_router.get("/get_files")
@audit_action("获取文件列表")
async def get_files(user_id: str):

    """
    
    args:

        user_id: str 用户ID

    return:

        files: List[str] 用户文件列表
    
    """
    try:
        if user_id is None or user_id.strip() == "":
            raise ValueError(f"user_id 为空或格式错误，user_id: {user_id}")
        file_path = os.path.join(DEFAULT_OUTPUT_FOLDER, user_id)
        if not os.path.exists(file_path):
            os.mkdir(file_path)
        files = os.listdir(file_path)
        return {"files": files}
    except Exception as e:
        return HTTPException(500, f"获取文件列表失败: {str(e)}")
    
# --------------------------
# 文件管理接口
# --------------------------
@powder_compounding_router.post("/upload")
@audit_action("上传文件")
async def upload_files(user_id: str,files: List[UploadFile] = File(...)):
    """
    
    args:

        user_id: str 用户ID

        files:  上传的文件列表

    return:

        uploaded_files: 上传成功的文件列表
    
    """
    if user_id is None or user_id.strip() == "":
        return HTTPException(500, f"文件上传失败，user_id 为空或格式错误，user_id: {user_id}")
    file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, user_id)
    if not os.path.exists(file_dir):
        os.mkdir(file_dir)
    saved_files = []
    for file in files:
        try:
            # 生成唯一文件名防止冲突
            filename = file.filename
            if not filename.lower().endswith(".txt"):
                raise HTTPException(400, "仅支持上传txt文件")
            filename = filename.replace(".txt", "")
            filepath = os.path.join(file_dir, filename)
            raw_data = await file.read()
            # 异步保存文件
            def save_file():
                result = chardet.detect(raw_data)
                encoding = result['encoding']
                extracted = []
                start = False
                texts = raw_data.decode(encoding)
                for i, line in enumerate(texts.split("\n")):
                    if line.startswith("0.033"):
                        start = True
                    if start:
                        extracted.append(line.strip())
                    if len(extracted) == 100:
                        break
                if extracted:
                    with open(filepath, "w", encoding="utf-8") as fw:
                        fw.write("\n".join(extracted))
            await run_in_thread(save_file)
            
            saved_files.append(filename)
        except Exception as e:
            raise HTTPException(500, f"文件{file.filename}上传失败: {str(e)}")
    
    return {"uploaded_files": saved_files}

@powder_compounding_router.get("/get_detile")
@audit_action("查看文件信息")
async def get_detile(
    user_id: str, filename: str
):
    """
    args:

        user_id: str 用户ID

        filename: str 文件名

    return:

        file_content: List[List[str]] 文件内容
        colunm_names: List[str] 列名
    """

    if user_id is None or user_id.strip() == "":
        raise HTTPException(500, f"获取文件{filename}信息失败，user_id 为空或格式错误，user_id: {user_id}")
    file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, user_id)
    if not os.path.exists(file_dir):
        os.mkdir(file_dir)

    filepath = os.path.join(file_dir, filename)
    if not os.path.exists(filepath):
        raise HTTPException(404, "文件不存在")
    try:
        extracted = []
        with open(filepath, "r", encoding="utf-8") as f:
            for i, line in enumerate(f.readlines()):
                    if line.startswith("0.033"):
                        start = True
                    if start:
                        res = re.split(r'[ \t]+', line.strip())
                        extracted.append(res)
                    if len(extracted) == 100:
                        break
        
        return {"file_content": extracted, "colunm_names": ["粒径", "分布", "累计"]}
    except Exception as e:
        raise HTTPException(500, f"读取文件失败: {str(e)}")

@powder_compounding_router.delete("/files/{filename}")
@audit_action("删除文件")
async def delete_file(user_id: str, filename: str):
    """
    
    args:

        user_id: str 用户ID

        filename: str 文件名

    return:

        status: 删除状态
    
    """
    if user_id is None or user_id.strip() == "":
        raise HTTPException(500, f"删除文件{filename}失败，user_id 为空或格式错误，user_id: {user_id}")
    file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, user_id)
    if not os.path.exists(file_dir):
        os.mkdir(file_dir)
    filepath = os.path.join(file_dir, filename)
    if not os.path.exists(filepath):
        raise HTTPException(404, "文件不存在")
    
    try:
        await run_in_thread(os.remove, filepath)
        return {"status": "success"}
    except Exception as e:
        raise HTTPException(500, f"删除失败: {str(e)}")

@powder_compounding_router.post("/rename")
@audit_action("文件重命名")
async def rename_file(user_id: str, old_name: str, new_name: str):
    """

    args:

        user_id: str 用户ID

        old_name:  str 旧文件名

        new_name: str 新文件名

    return:

        status: 重命名状态

    """
    if user_id is None or user_id.strip() == "":
        raise HTTPException(500, f"user_id 为空或格式错误，user_id: {user_id}")
    file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, user_id)
    if not os.path.exists(file_dir):
        os.mkdir(file_dir)
    old_path = os.path.join(file_dir, old_name)
    # if not new_name.lower().endswith(".txt"):
    #     new_name += ".txt"
    new_path = os.path.join(file_dir, new_name)
    
    if not os.path.exists(old_path):
        raise HTTPException(404, "文件不存在")
    if os.path.exists(new_path):
        raise HTTPException(404, "新文件名已存在")
    try:
        await run_in_thread(os.rename, old_path, new_path)
        return {"status": "success"}
    except Exception as e:
        raise HTTPException(500, f"重命名失败: {str(e)}")


@powder_compounding_router.post("/merge")
@audit_action("文件合并")
async def merge_files(request: MergeRequest):
    """
    
    args:
    
        filenames: List[str] 文件名列表
 
        user_id: str   用户id
 
        new_filename: str = None

    return:

        new_filename: 新文件名
    
    """
    if request.user_id is None or request.user_id.strip() == "":
        raise HTTPException(500, f"user_id 为空或格式错误，user_id: {request.user_id}")
    # 验证文件存在
    file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, request.user_id)
    if not os.path.exists(file_dir):
        os.mkdir(file_dir)
    for fname in request.filenames:
        if not os.path.exists(os.path.join(file_dir, fname)):
            raise HTTPException(404, f"文件{fname}不存在")
    
    # 生成默认新文件名
    new_name = request.new_filename or f"merged_{uuid.uuid4().hex[:8]}.txt"
    new_path = os.path.join(file_dir, new_name)
    
    try:
        # 异步执行合并操作
        def do_merge():
            dfs = []
            for fname in request.filenames:
                df = pd.read_csv(
                    os.path.join(file_dir, fname),
                    sep=r"\s+", header=None, engine="python"
                )
                dfs.append(df.values)
            
            merged = np.mean(dfs, axis=0)
            np.savetxt(new_path, merged, fmt="%.3f", delimiter="\t")
        
        await run_in_thread(do_merge)
        return {"new_filename": new_name}
    except Exception as e:
        raise HTTPException(500, f"合并失败: {str(e)}")

# --------------------------
# 计算相关接口
# --------------------------
@powder_compounding_router.post("/calculate/curve-intersections")
@audit_action("绘制粉体复配图表")
async def calculate_curve_intersections(request: IntersectionRequest):
    """
    
    args:  

        filenames: List[str] 文件名列表

        user_id: str 用户id

        y_value: float = 0.5  # 仅horizontal-intersections接口需要， 这里可以不传

    return:

        intersections: List[Tuple[float, float, str]] 交点列表,最后的值是谁和谁的交点//分割

        curve_data: Dict[str, Tuple[List[float], List[float]]] 曲线数据

    """
    if request.user_id is None or request.user_id.strip() == "":
        raise HTTPException(500, f"user_id 为空或格式错误，user_id: {request.user_id}")
    try:
        
        file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, request.user_id)
        if not os.path.exists(file_dir):
            os.mkdir(file_dir)
        # 读取所有文件数据
        curve_data = {}
        cal_data = {}
        for fname in request.filenames:
            df = pd.read_csv(
                os.path.join(file_dir, fname),
                sep=r"\s+", header=None, engine="python"
            )
            original_diameter = df.iloc[:, 0].astype(float)
            interval = df.iloc[:, 1].astype(float)
            log_diameter = np.log10(original_diameter)
            # log_diameter = original_diameter
            curve_data[fname] = (log_diameter.values.tolist(), interval.values.tolist())
            cal_data[fname] = (original_diameter.values.tolist(), interval.values.tolist())
        
        # 计算交点
        intersections = find_curve_intersections(cal_data)
        return {"intersections": intersections, "curve_data": curve_data}
    except Exception as e:
        raise HTTPException(500, f"计算失败: {str(e)}")

@powder_compounding_router.post("/calculate/horizontal-intersections")
@audit_action("绘制粉体复配横线")
async def calculate_horizontal_intersections(request: IntersectionRequest):

    """
    
    args:

        filenames: List[str] 文件名列表

        user_id: str 用户id

        y_value: float = 0.5  # 一定传 默认0。5

    return:

        intersections: List[Tuple[float, float, str]] 交点列表,最后的值是谁和谁的交点//分割



    """
    if request.user_id is None or request.user_id.strip() == "":
        raise HTTPException(500, f"user_id 为空或格式错误，user_id: {request.user_id}")
    try:
        file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, request.user_id)
        if not os.path.exists(file_dir):
            os.mkdir(file_dir)
        if request.y_value is None:
            raise ValueError("必须提供y_value参数")
        
        # 读取文件数据
        curve_data = {}
        cal_data = {}
        for fname in request.filenames:
            df = pd.read_csv(
                os.path.join(file_dir, fname),
                sep=r"\s+", header=None, engine="python"
            )
            original_diameter = df.iloc[:, 0].astype(float)
            interval = df.iloc[:, 1].astype(float)
            log_diameter = np.log10(original_diameter)
            # log_diameter = original_diameter
            curve_data[fname] = (log_diameter.values.tolist(), interval.values.tolist())
            cal_data[fname] = (original_diameter.values, interval.values.tolist())
        
        # 计算交点
        intersections = find_horizontal_intersections(cal_data, request.y_value)
        return {"intersections": intersections}
    except Exception as e:
        raise HTTPException(500, f"计算失败: {str(e)}")

@powder_compounding_router.post("/calculate/points")
@audit_action("粉体复配交点计算")
async def calculate_points(request: CalculationRequest):

    """
    这个接口可以不调用，调用v2版本的接口即可
    
    args:

        filenames: List[str] 文件名列表

        user_id: str 用户id

        selected_indices: List[int] 选中的交点索引列表

        m_value: float = 0.37

        y_value: float = 0.5  # 可选的水平线Y值

    return:

        results: List[Dict] 计算结果列表

        deltas: List[float] 差值列表
    
    """
    if request.user_id is None or request.user_id.strip() == "":
        raise HTTPException(500, f"user_id 为空或格式错误，user_id: {request.user_id}")
    try:
        file_dir = os.path.join(DEFAULT_OUTPUT_FOLDER, request.user_id)
        if not os.path.exists(file_dir):
            os.mkdir(file_dir)
        # 获取所有交点（曲线+水平线）
        curve_data = {}
        for fname in request.filenames:
            df = pd.read_csv(
                os.path.join(file_dir, fname),
                sep=r"\s+", header=None, engine="python"
            )
            original_diameter = df.iloc[:, 0].astype(float)
            interval = df.iloc[:, 1].astype(float)
            log_diameter = np.log10(original_diameter)
            curve_data[fname] = (log_diameter.values, interval.values)
        
        curve_intersections = find_curve_intersections(curve_data)
        if request.y_value:
            horizontal_intersections = find_horizontal_intersections(curve_data, request.y_value)
        else:
            horizontal_intersections = []
        
        all_points = curve_intersections + horizontal_intersections
        selected_points = [all_points[i] for i in request.selected_indices]
        
        # 执行计算
        results, deltas = perform_calculation(selected_points, request.m_value)
        return {
            "results": results,
            "deltas": deltas
        }
    except Exception as e:
        raise HTTPException(500, f"计算失败: {str(e)}")



@powder_compounding_router.post("/calculate/points-v2")
@audit_action("粉体复配交点计算")
async def calculate_points(request: CalculationRequest2):
    """
    
    args:

        selected_points: List[Tuple[float, float, str]] 选中的交点列表 里面是每个交点的横纵坐标值,以及交点的名称

        m_value: float = 0.37   幂指数

        weight_infos: dict[str, float] 选择传  键是文件名称 值是对应的密度


    return:
        results: List[Dict] 计算结果列表

        deltas: List[float] 差值列表
    
    """
    try:
        # 执行计算
        results, deltas = perform_calculation(request.selected_points, request.m_value)
        
        weight_infos = request.weight_infos
        if weight_infos is not None and "未知材料" not in "".join(deltas.keys()):
            wv = cal_weight(weight_infos, deltas)
        else:
            wv = {k:(v, 0) for k,v in deltas.items()}
        return {
            "results": results,
            "deltas": wv
        }
    except Exception as e:
        raise HTTPException(500, f"计算失败: {str(e)}")

# --------------------------
# 核心计算函数（重构版）
# --------------------------
def find_curve_intersections(curve_data: Dict) -> List[Tuple[float, float]]:
    intersections = []
    # intersections = {}
    keys = list(curve_data.keys())
    
    for i in range(len(keys)):
        for j in range(i + 1, len(keys)):
            x1, y1 = curve_data[keys[i]]
            x2, y2 = curve_data[keys[j]]
            
            for k in range(len(x1) - 1):
                key = "//".join([keys[i], keys[j]])
                if (y1[k] - y2[k]) * (y1[k + 1] - y2[k + 1]) < 0:
                    dx = x1[k + 1] - x1[k]
                    dy1 = y1[k + 1] - y1[k]
                    dy2 = y2[k + 1] - y2[k]
                    t = (y2[k] - y1[k]) / ((dy1 - dy2) + 1e-9)
                    xi = x1[k] + t * dx
                    yi = y1[k] + t * dy1
                    # intersections.append((10 ** float(xi), float(yi), key))
                    intersections.append((float(xi), float(yi), key))
                    
    return intersections

def find_horizontal_intersections(curve_data: Dict, y_value: float) -> List[Tuple[float, float]]:
    intersections = []
    for name, (x_vals, y_vals) in curve_data.items():
        key = "//".join([name, f"横线_{y_value}"])
        # if key not in intersections:
        #     intersections[key] = []
        for i in range(len(x_vals) - 1):
            if (y_vals[i] - y_value) * (y_vals[i + 1] - y_value) < 0:
                t = (y_value - y_vals[i]) / (y_vals[i + 1] - y_vals[i])
                x_intersect = x_vals[i] + t * (x_vals[i + 1] - x_vals[i])
                intersections.append((x_intersect, y_value, key))
    
    return intersections


def cal_weight(weight_infos:dict, deltas:dict):
    res = {}
    
    all_weight = sum([weight_infos[k]*v for k,v in deltas.items()])
    if all_weight != 0:
        for k, v in deltas.items():
            res[k] = (v, weight_infos[k] * v / all_weight)
        return res
    res = {k:(v,0) for k,v in deltas.items()}
    return res
    

def perform_calculation(points: List[Tuple[float, float]], m: float):
    points.sort(key=lambda p: p[0])  # 按X排序
    x1 = points[0][0]
    xn1 = points[-1][0]
    denom = xn1**m - x1**m
    
    if denom == 0:
        raise ValueError("分母为零，无法计算")
    
    results = []

    for x, y, name in points:
        value = (x**m - x1**m) / denom
        results.append({"x": x, "y": y, "value": value, "name":name})
    
    deltas = {}
    last_name = ''
    for i in range(len(results)-1):
        name1,name2 = results[i]['name'].split('//')
        
        if "横线_" in name1:
            name = name2
        elif "横线_" in name2:
            name = name1
        elif last_name == name1:
            name = name2
        elif last_name == name2:
            name = name1
        else:
            name = f'未知材料_{i+1}'
        last_name = name
        deltas[name] = results[i+1]["value"] - results[i]["value"]
    
    return results, deltas


